﻿#include "CIEChromaticityPlot.h"
#include "qcustomplot.h"

#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#include <QtMath>
#include <QResizeEvent>
#include <QPainter>

static inline bool isFinite2(double a, double b){ return qIsFinite(a)&&qIsFinite(b); }

CIEChromaticityPlot::CIEChromaticityPlot(QWidget *parent)
    : QWidget(parent)
{
    // 避开个别环境的 setLayout 崩溃路径：不用布局，子控件充满
    plot_ = new QCustomPlot(this);
    plot_->setGeometry(rect());
    setupPlot();
}

void CIEChromaticityPlot::resizeEvent(QResizeEvent *e)
{
    QWidget::resizeEvent(e);
    if (plot_) plot_->setGeometry(rect());
}

void CIEChromaticityPlot::setupPlot()
{
    // 默认 1931 坐标
    plot_->xAxis->setLabel("x");
    plot_->yAxis->setLabel("y");
    plot_->xAxis->setRange(0.0, 0.8);
    plot_->yAxis->setRange(0.0, 0.9);

    plot_->axisRect()->setupFullAxesBox();
    plot_->xAxis->grid()->setSubGridVisible(true);
    plot_->yAxis->grid()->setSubGridVisible(true);

    // 禁用缩放/拖动
    plot_->setInteractions(QCP::iNone);

    // 背景填充图像设置
    plot_->axisRect()->setBackgroundScaled(true);
    plot_->axisRect()->setBackgroundScaledMode(Qt::IgnoreAspectRatio);

    // 曲线对象
    locusCurve_ = new QCPCurve(plot_->xAxis, plot_->yAxis);
    locusCurve_->setPen(QPen(QColor(20,20,20), 1.5));

    purpleEdge_ = new QCPCurve(plot_->xAxis, plot_->yAxis);
    { QPen p(QColor(130,0,180)); p.setStyle(Qt::DashLine); p.setWidthF(1.2); purpleEdge_->setPen(p); }

    planckCurve_ = new QCPCurve(plot_->xAxis, plot_->yAxis);
    planckCurve_->setPen(QPen(QColor(0,120,200), 1.3));

    isoCCTGraph_ = plot_->addGraph();
    isoCCTGraph_->setPen(QPen(QColor(0,120,200,160), 1.0));

    srgbCurve_ = new QCPCurve(plot_->xAxis, plot_->yAxis);
    srgbCurve_->setPen(QPen(QColor(220, 60, 60), 1.2));

    adobeCurve_ = new QCPCurve(plot_->xAxis, plot_->yAxis);
    adobeCurve_->setPen(QPen(QColor(40, 160, 40), 1.2));

    domWLGraph_ = plot_->addGraph();
    domWLGraph_->setPen(QPen(QColor(120, 120, 120), 1.0));

    plot_->legend->setVisible(false);
}

bool CIEChromaticityPlot::loadCMF(DiagramKind kind, const QString &csvPath)
{
    QFile f(csvPath);
    if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
    { emit cmfLoaded(kind,false); return false; }

    QVector<CMFRow> dst;
    QTextStream in(&f);
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
    in.setCodec("UTF-8");
#endif
    bool firstLine=true;
    while (!in.atEnd()){
        const QString line = in.readLine().trimmed();
        if (line.isEmpty()) continue;
        if (firstLine){ firstLine=false; if (!line[0].isDigit()) continue; }
        const QStringList cols = line.split(QRegularExpression("\\s*,\\s*"));
        if (cols.size()<4) continue;
        bool ok1=false,ok2=false,ok3=false,ok4=false;
        const double wl   = cols[0].toDouble(&ok1);
        const double xbar = cols[1].toDouble(&ok2);
        const double ybar = cols[2].toDouble(&ok3);
        const double zbar = cols[3].toDouble(&ok4);
        if (ok1 && ok2 && ok3 && ok4) dst.push_back({wl,xbar,ybar,zbar});
    }
    f.close();

    std::sort(dst.begin(), dst.end(), [](const CMFRow&a, const CMFRow&b){ return a.wl < b.wl; });
    dst.erase(std::unique(dst.begin(), dst.end(), [](const CMFRow&a, const CMFRow&b){ return a.wl==b.wl; }), dst.end());

    if (dst.isEmpty()){ emit cmfLoaded(kind,false); return false; }

    if (kind==DiagramKind::CIE1931_xy || kind==DiagramKind::CIE1976_uv) cmf1931_ = dst;
    else cmf2015_ = dst;

    emit cmfLoaded(kind,true);
    rebuildAll();
    return true;
}

void CIEChromaticityPlot::setDiagram(DiagramKind kind)
{
    kind_ = kind;
    if (kind_==DiagramKind::CIE1931_xy){
        plot_->xAxis->setLabel("x");
        plot_->yAxis->setLabel("y");
        plot_->xAxis->setRange(0.0, 0.8);
        plot_->yAxis->setRange(0.0, 0.9);
    } else {
        plot_->xAxis->setLabel("u'");
        plot_->yAxis->setLabel("v'");
        // 初值会被 autoRangeToLocus() 覆盖
        plot_->xAxis->setRange(0.0, 0.65);
        plot_->yAxis->setRange(0.0, 0.65);
    }
    rebuildAll();
}

void CIEChromaticityPlot::rebuildAll()
{
    clearWavelengthAnnotations();
    buildSpectralLocus();

    // 1976/2015 自动取景，避免显示不全
    if (kind_ != DiagramKind::CIE1931_xy)
        autoRangeToLocus();

    updateChromaticityBackground();
    buildGamutTriangles();
    buildPlanckAndIsoCCT();
    buildDominantWavelengthLines();
    annotateWavelengths();

    plot_->replot();
}

void CIEChromaticityPlot::buildSpectralLocus()
{
    const QVector<CMFRow> &cmf = (kind_==DiagramKind::CIE2015_uv) ? cmf2015_ : cmf1931_;
    if (cmf.isEmpty()){
        locusPoly_.clear();
        locusCurve_->data()->clear();
        purpleEdge_->data()->clear();
        return;
    }
    QVector<QCPCurveData> data; data.reserve(cmf.size());
    locusPoly_.clear(); locusPoly_.reserve(cmf.size()+1);

    for (int i=0;i<cmf.size();++i){
        double X=cmf[i].xbar, Y=cmf[i].ybar, Z=cmf[i].zbar;
        QPointF p = projectFromXYZ(X,Y,Z);
        if (!isFinite2(p.x(),p.y())) continue;
        data.push_back(QCPCurveData(i, p.x(), p.y()));
        locusPoly_ << p;
    }
    if (!locusPoly_.isEmpty() && locusPoly_.first()!=locusPoly_.last())
        locusPoly_ << locusPoly_.first();

    locusCurve_->data()->set(data, true);

    purpleEdge_->data()->clear();
    if (!data.isEmpty()){
        const auto &first = data.first();
        const auto &last  = data.last();
        QVector<QCPCurveData> edge;
        edge << QCPCurveData(0, last.key, last.value)
             << QCPCurveData(1, first.key, first.value);
        purpleEdge_->data()->set(edge, true);
    }
}

void CIEChromaticityPlot::updateChromaticityBackground()
{
    if (locusPoly_.isEmpty()) {
        plot_->axisRect()->setBackground(QBrush(Qt::white));
        return;
    }

    const double xmin = plot_->xAxis->range().lower;
    const double xmax = plot_->xAxis->range().upper;
    const double ymin = plot_->yAxis->range().lower;
    const double ymax = plot_->yAxis->range().upper;

    const int width  = 900;
    const int height = int((ymax - ymin)/(xmax - xmin) * width + 0.5);
    if (width<=0 || height<=0){ plot_->axisRect()->setBackground(QBrush(Qt::white)); return; }

    auto toImage = [&](double ax, double ay)->QPoint{
        int ix = int( (ax - xmin) / (xmax - xmin) * (width -1) + 0.5 );
        int iy = int( (ymax - ay) / (ymax - ymin) * (height-1) + 0.5 );
        ix = qBound(0, ix, width-1);
        iy = qBound(0, iy, height-1);
        return QPoint(ix,iy);
    };

    // 掩膜
    QImage mask(width,height,QImage::Format_Alpha8); mask.fill(0);
    QPolygon polyImg; polyImg.reserve(locusPoly_.size());
    for (const auto &p: locusPoly_) polyImg << toImage(p.x(), p.y());
    if (polyImg.size()<3){ plot_->axisRect()->setBackground(QBrush(Qt::white)); return; }
    {
        QPainter qp(&mask);
        qp.setPen(Qt::NoPen); qp.setBrush(Qt::white);
        qp.drawPolygon(polyImg, Qt::OddEvenFill);
        qp.end();
    }

    // 背景
    QImage img(width,height,QImage::Format_RGB32); img.fill(Qt::white);
    QRect bbox = polyImg.boundingRect().intersected(QRect(0,0,width,height));

    for (int j=bbox.top(); j<=bbox.bottom(); ++j){
        for (int i=bbox.left(); i<=bbox.right(); ++i){
            if (qAlpha(mask.pixel(i,j))==0) continue;

            const double ax = xmin + (i + 0.5)*(xmax - xmin)/width;
            const double ay = ymax - (j + 0.5)*(ymax - ymin)/height;

            double x, y;
            if (!xyFromCurrentAxes(ax, ay, x, y)) continue;
            if (!isFinite2(x,y)) continue;

            const QColor c = sRGBfromxyY(x,y,1.0);
            img.setPixel(i,j,c.rgb());
        }
    }

    bgImage_ = img;
    plot_->axisRect()->setBackground(QPixmap::fromImage(bgImage_));
}

void CIEChromaticityPlot::buildPlanckAndIsoCCT()
{
    planckCurve_->data()->clear();
    isoCCTGraph_->data()->clear();
    if (!(showPlanck_ || showIsoCCT_)) return;

    const QVector<CMFRow> &cmf = (kind_==DiagramKind::CIE2015_uv) ? cmf2015_ : cmf1931_;
    if (cmf.isEmpty()) return;

    auto projT = [&](double T)->QPointF{
        double X=0,Y=0,Z=0;
        for (const auto& r: cmf){
            const double p = blackbodyRel(r.wl, T);
            X += p*r.xbar; Y += p*r.ybar; Z += p*r.zbar;
        }
        return projectFromXYZ(X,Y,Z);
    };

    // 普朗克轨迹
    if (showPlanck_) {
        QVector<QCPCurveData> pData; pData.reserve(300);
        for (int T=1000; T<=25000; T+=100){
            QPointF p = projT(double(T));
            if (isFinite2(p.x(),p.y()))
                pData.push_back(QCPCurveData(T, p.x(), p.y()));
        }
        planckCurve_->data()->set(pData, true);
    }

    // iso-CCT 短线
    if (showIsoCCT_){
        QVector<double> Tset = {2500, 3000, 3500, 4000, 5000, 6500, 10000, 20000};
        QVector<double> xs, ys;
        const double dT=50.0;
        const double L_xy = 0.02;
        const double L_uv = 0.008;

        for (double T: Tset){
            QPointF pm = projT(T-dT), p0=projT(T), pp=projT(T+dT);
            if (!isFinite2(pm.x(),pm.y()) || !isFinite2(p0.x(),p0.y()) || !isFinite2(pp.x(),pp.y())) continue;
            QPointF tanv = pp - pm;
            double len = std::hypot(tanv.x(), tanv.y()); if (len<=0) continue;
            QPointF n = QPointF(-tanv.y()/len, tanv.x()/len);
            const double L = (kind_==DiagramKind::CIE1931_xy) ? L_xy : L_uv;
            QPointF a = p0 - n*L*0.5;
            QPointF b = p0 + n*L*0.5;
            xs << a.x(); ys << a.y();
            xs << b.x(); ys << b.y();
            xs << qQNaN(); ys << qQNaN();
        }
        if (!xs.isEmpty()) isoCCTGraph_->setData(xs, ys, true);
    }
}

void CIEChromaticityPlot::buildGamutTriangles()
{
    srgbCurve_->data()->clear();
    adobeCurve_->data()->clear();

    auto toAx = [&](double x, double y)->QPointF{ return projectFromxy(x,y); };

    const double xw=0.3127, yw=0.3290;
    Q_UNUSED(xw); Q_UNUSED(yw);

    if (showSRGB_){
        struct P{ double x,y; };
        P R{0.6400,0.3300}, G{0.3000,0.6000}, B{0.1500,0.0600};
        QVector<QCPCurveData> poly;
        QPointF pr=toAx(R.x,R.y), pg=toAx(G.x,G.y), pb=toAx(B.x,B.y);
        poly << QCPCurveData(0, pr.x(),pr.y())
             << QCPCurveData(1, pg.x(),pg.y())
             << QCPCurveData(2, pb.x(),pb.y())
             << QCPCurveData(3, pr.x(),pr.y());
        srgbCurve_->data()->set(poly, true);
    }

    if (showAdobe_){
        struct P{ double x,y; };
        P R{0.6400,0.3300}, G{0.2100,0.7100}, B{0.1500,0.0600};
        QVector<QCPCurveData> poly;
        QPointF pr=toAx(R.x,R.y), pg=toAx(G.x,G.y), pb=toAx(B.x,B.y);
        poly << QCPCurveData(0, pr.x(),pr.y())
             << QCPCurveData(1, pg.x(),pg.y())
             << QCPCurveData(2, pb.x(),pb.y())
             << QCPCurveData(3, pr.x(),pr.y());
        adobeCurve_->data()->set(poly, true);
    }
}

void CIEChromaticityPlot::buildDominantWavelengthLines()
{
    domWLGraph_->data()->clear();
    if (!showDomWL_) return;

    const QVector<CMFRow> &cmf = (kind_==DiagramKind::CIE2015_uv) ? cmf2015_ : cmf1931_;
    if (cmf.isEmpty()) return;

    const double xw=0.3127, yw=0.3290;
    QPointF pw = projectFromxy(xw,yw);
    QVector<double> xs, ys;

    QVector<double> W = {380,400,420,440,460,480,500,520,540,560,580,600,620,650,680,700};

    auto locusAt = [&](double wl)->QPointF{
        double xb,yb,zb; if (!cmfAt(cmf, wl, xb,yb,zb)) return QPointF(qQNaN(),qQNaN());
        return projectFromXYZ(xb,yb,zb);
    };

    for (double wl: W){
        QPointF p = locusAt(wl);
        if (!isFinite2(p.x(),p.y()) || !isFinite2(pw.x(),pw.y())) continue;
        xs << pw.x(); ys << pw.y();
        xs << p.x();  ys << p.y();
        xs << qQNaN(); ys << qQNaN();
    }
    if (!xs.isEmpty()) domWLGraph_->setData(xs, ys, true);
}

bool CIEChromaticityPlot::setSpectrum(const QVector<double> &wavelength_nm,
                                      const QVector<double> &power)
{
    if (spectrumTracer_) { plot_->removeItem(spectrumTracer_); spectrumTracer_ = nullptr; }
    if (spectrumLabel_)  { plot_->removeItem(spectrumLabel_);  spectrumLabel_  = nullptr; }

    const QVector<CMFRow> &cmf = (kind_==DiagramKind::CIE2015_uv) ? cmf2015_ : cmf1931_;
    if (cmf.isEmpty()) return false;

    double X=0,Y=0,Z=0;
    if (!integrateSPD(cmf, wavelength_nm, power, X,Y,Z)) return false;

    QPointF a = projectFromXYZ(X,Y,Z);
    if (!isFinite2(a.x(),a.y())) return false;

    spectrumTracer_ = new QCPItemTracer(plot_);
    spectrumTracer_->position->setType(QCPItemPosition::ptPlotCoords);
    spectrumTracer_->position->setCoords(a.x(), a.y());
    spectrumTracer_->setStyle(QCPItemTracer::tsCircle);
    spectrumTracer_->setSize(7);
    spectrumTracer_->setPen(QPen(Qt::black, 1.0));
    spectrumTracer_->setBrush(QBrush(Qt::white));

    double x,y; xyFromCurrentAxes(a.x(),a.y(),x,y);
    spectrumLabel_ = new QCPItemText(plot_);
    spectrumLabel_->position->setType(QCPItemPosition::ptPlotCoords);
    spectrumLabel_->position->setCoords(a.x()+0.012, a.y()+0.012);
    spectrumLabel_->setClipAxisRect(plot_->axisRect());
    spectrumLabel_->setFont(QFont(plot_->xAxis->labelFont().family(), 9));
    spectrumLabel_->setPen(QPen(Qt::black));
    spectrumLabel_->setBrush(QBrush(QColor(255,255,255,200)));
    spectrumLabel_->setPadding(QMargins(3,2,3,2));
    spectrumLabel_->setText(QString("%1=%2  %3=%4  (xy=%5,%6)")
                                .arg((kind_==DiagramKind::CIE1931_xy)?"x":"u′")
                                .arg(a.x(),0,'f',4)
                                .arg((kind_==DiagramKind::CIE1931_xy)?"y":"v′")
                                .arg(a.y(),0,'f',4)
                                .arg(x,0,'f',4).arg(y,0,'f',4));

    plot_->replot();
    return true;
}

void CIEChromaticityPlot::addPoint_xy(double x, double y, const QString &label)
{
    QPointF a = projectFromxy(x,y);
    auto *tr = new QCPItemTracer(plot_);
    tr->position->setType(QCPItemPosition::ptPlotCoords);
    tr->position->setCoords(a.x(), a.y());
    tr->setStyle(QCPItemTracer::tsCrosshair);
    tr->setSize(9);
    tr->setPen(QPen(Qt::darkRed, 1.2));

    QCPItemText *txt = nullptr;
    if (!label.isEmpty()) {
        txt = new QCPItemText(plot_);
        txt->position->setType(QCPItemPosition::ptPlotCoords);
        txt->position->setCoords(a.x()+0.01, a.y()+0.01);
        txt->setClipAxisRect(plot_->axisRect());
        txt->setFont(QFont(plot_->xAxis->labelFont().family(), 9));
        txt->setPen(QPen(Qt::darkRed));
        txt->setBrush(QBrush(QColor(255,255,255,200)));
        txt->setPadding(QMargins(3,2,3,2));
        txt->setText(label);
    }
    markers_.push_back({tr, txt});
    plot_->replot();
}

void CIEChromaticityPlot::clearPoints()
{
    for (auto &m : markers_) {
        if (m.tracer) plot_->removeItem(m.tracer);
        if (m.label)  plot_->removeItem(m.label);
    }
    markers_.clear();
    plot_->replot();
}

void CIEChromaticityPlot::clearSpectrumPoint()
{
    if (spectrumTracer_) { plot_->removeItem(spectrumTracer_); spectrumTracer_ = nullptr; }
    if (spectrumLabel_)  { plot_->removeItem(spectrumLabel_);  spectrumLabel_  = nullptr; }
    plot_->replot();
}

// 覆盖物开关
void CIEChromaticityPlot::setShowSRGB(bool on){ showSRGB_=on; buildGamutTriangles(); plot_->replot(); }
void CIEChromaticityPlot::setShowAdobeRGB(bool on){ showAdobe_=on; buildGamutTriangles(); plot_->replot(); }
void CIEChromaticityPlot::setShowPlanck(bool on){ showPlanck_=on; buildPlanckAndIsoCCT(); plot_->replot(); }
void CIEChromaticityPlot::setShowIsoCCT(bool on){ showIsoCCT_=on; buildPlanckAndIsoCCT(); plot_->replot(); }
void CIEChromaticityPlot::setShowDominantLines(bool on){ showDomWL_=on; buildDominantWavelengthLines(); plot_->replot(); }

// 波长标注
void CIEChromaticityPlot::clearWavelengthAnnotations()
{
    for (auto *t : wlLabels_) if (t) plot_->removeItem(t);
    for (auto *d : wlDots_)   if (d) plot_->removeItem(d);
    wlLabels_.clear();
    wlDots_.clear();
}

void CIEChromaticityPlot::annotateWavelengths()
{
    const QVector<CMFRow> &cmf = (kind_==DiagramKind::CIE2015_uv) ? cmf2015_ : cmf1931_;
    if (cmf.isEmpty() || locusPoly_.size()<3) return;

    const QVector<double> W = {380,400,420,440,460,480,500,520,540,560,580,600,620,650,680,700};

    // 多边形重心用于“朝外偏移”
    QPointF c(0,0);
    for (const auto& p : locusPoly_) { c.rx() += p.x(); c.ry() += p.y(); }
    c /= double(locusPoly_.size());

    auto projWL = [&](double wl)->QPointF{
        double xb,yb,zb; if (!cmfAt(cmf, wl, xb,yb,zb)) return QPointF(qQNaN(),qQNaN());
        return projectFromXYZ(xb,yb,zb);
    };

    for (double wl : W)
    {
        QPointF p = projWL(wl);
        if (!isFinite2(p.x(),p.y())) continue;

        QPointF v = p - c;
        double len = std::hypot(v.x(), v.y());
        if (len <= 0) continue;
        v /= len;
        const double offset = (kind_==DiagramKind::CIE1931_xy) ? 0.015 : 0.006;
        QPointF lblPos = p + v * offset;

        auto *dot = new QCPItemTracer(plot_);
        dot->position->setType(QCPItemPosition::ptPlotCoords);
        dot->position->setCoords(p.x(), p.y());
        dot->setStyle(QCPItemTracer::tsCircle);
        dot->setSize(5);
        dot->setPen(QPen(Qt::black, 0.8));
        dot->setBrush(Qt::white);
        wlDots_.push_back(dot);

        auto *txt = new QCPItemText(plot_);
        txt->position->setType(QCPItemPosition::ptPlotCoords);
        txt->position->setCoords(lblPos.x(), lblPos.y());
        txt->setClipAxisRect(plot_->axisRect());
        txt->setPadding(QMargins(2,1,2,1));
        txt->setBrush(QColor(255,255,255,210));
        txt->setPen(QPen(Qt::black));
        txt->setFont(QFont(plot_->xAxis->labelFont().family(), 8));
        txt->setText(QString::number(int(wl)));
        wlLabels_.push_back(txt);
    }
}

// 自动取景（u′v′）
void CIEChromaticityPlot::autoRangeToLocus()
{
    if (locusPoly_.size() < 3) return;
    double minx=1e9, maxx=-1e9, miny=1e9, maxy=-1e9;
    for (const auto& p : locusPoly_) {
        minx = qMin(minx, p.x());  maxx = qMax(maxx, p.x());
        miny = qMin(miny, p.y());  maxy = qMax(maxy, p.y());
    }
    const double padx = (maxx-minx)*0.08;
    const double pady = (maxy-miny)*0.08;
    plot_->xAxis->setRange(minx - padx, maxx + padx);
    plot_->yAxis->setRange(miny - pady, maxy + pady);
}

// —— 颜色学/投影/数值工具 —— //
bool CIEChromaticityPlot::xyz2xy(double X, double Y, double Z, double &x, double &y)
{
    const double S = X+Y+Z; if (S<=0) return false;
    x = X/S; y = Y/S; return true;
}
void CIEChromaticityPlot::xyz2uvprime(double X, double Y, double Z, double &up, double &vp)
{
    const double d = (X + 15*Y + 3*Z);
    if (d<=0){ up=vp=qQNaN(); return; }
    up = 4*X/d; vp = 9*Y/d;
}
bool CIEChromaticityPlot::uvp2xy(double up, double vp, double &x, double &y)
{
    const double d = (6*up - 16*vp + 12);
    if (d==0) return false;
    x = 9*up/d;
    y = 4*vp/d;
    return (x>=0 && y>=0 && x<=1 && y<=1);
}
bool CIEChromaticityPlot::cmfAt(const QVector<CMFRow>& cmf, double wl, double &xbar, double &ybar, double &zbar) const
{
    if (cmf.isEmpty()) return false;
    if (wl <= cmf.front().wl){ xbar=cmf.front().xbar; ybar=cmf.front().ybar; zbar=cmf.front().zbar; return true; }
    if (wl >= cmf.back().wl ){ xbar=cmf.back().xbar;  ybar=cmf.back().ybar;  zbar=cmf.back().zbar;  return true; }
    int lo=0, hi=cmf.size()-1;
    while (hi-lo>1){ int mid=(lo+hi)/2; if (cmf[mid].wl<=wl) lo=mid; else hi=mid; }
    const auto &L = cmf[lo], &H = cmf[hi];
    const double t = (wl - L.wl) / (H.wl - L.wl);
    xbar = L.xbar + t*(H.xbar - L.xbar);
    ybar = L.ybar + t*(H.ybar - L.ybar);
    zbar = L.zbar + t*(H.zbar - L.zbar);
    return true;
}
bool CIEChromaticityPlot::integrateSPD(const QVector<CMFRow>& cmf,
                                       const QVector<double> &wavelength_nm,
                                       const QVector<double> &power,
                                       double &X, double &Y, double &Z) const
{
    if (wavelength_nm.size()!=power.size() || wavelength_nm.isEmpty() || cmf.isEmpty())
        return false;
    X=Y=Z=0;
    for (int i=0;i<wavelength_nm.size();++i){
        double xb,yb,zb;
        if (!cmfAt(cmf, wavelength_nm[i], xb,yb,zb)) continue;
        const double p = power[i];
        X += p*xb; Y += p*yb; Z += p*zb;
    }
    return (X+Y+Z)>0;
}
double CIEChromaticityPlot::blackbodyRel(double wl_nm, double T_K)
{
    const double wl = wl_nm*1e-9; // m
    const double c2 = 1.438776877e-2; // m*K
    const double t  = c2/(wl*T_K);
    const double e  = qExp(t);
    return (e>1.0) ? (1.0/(qPow(wl,5)*(e-1.0))) : 0.0;
}
QPointF CIEChromaticityPlot::projectFromXYZ(double X, double Y, double Z) const
{
    if (kind_==DiagramKind::CIE1931_xy){
        double x,y; if (!xyz2xy(X,Y,Z,x,y)) return QPointF(qQNaN(),qQNaN());
        return QPointF(x,y);
    } else {
        double up,vp; xyz2uvprime(X,Y,Z,up,vp);
        return QPointF(up,vp);
    }
}
QPointF CIEChromaticityPlot::projectFromxy(double x, double y) const
{
    if (kind_==DiagramKind::CIE1931_xy) return QPointF(x,y);
    const double Y=1.0;
    const double X=(x/y)*Y;
    const double Z=((1.0-x-y)/y)*Y;
    double up,vp; xyz2uvprime(X,Y,Z,up,vp);
    return QPointF(up,vp);
}
bool CIEChromaticityPlot::xyFromCurrentAxes(double ax, double ay, double &x, double &y) const
{
    if (kind_==DiagramKind::CIE1931_xy){ x=ax; y=ay; return (x>=0&&y>=0&&x<=1&&y<=1); }
    return uvp2xy(ax, ay, x, y);
}

// sRGB 伽马
static inline double srgbCompand(double cLin)
{
    if (cLin <= 0.0) return 0.0;
    if (cLin >= 1.0) return 1.0;
    if (cLin <= 0.0031308) return 12.92 * cLin;
    return 1.055 * qPow(cLin, 1.0/2.4) - 0.055;
}
QColor CIEChromaticityPlot::sRGBfromxyY(double x, double y, double Y)
{
    if (y <= 0.0) return QColor(255,255,255);
    const double X = (x / y) * Y;
    const double Z = ((1.0 - x - y) / y) * Y;
    double Rl =  3.2406*X + (-1.5372)*Y + (-0.4986)*Z;
    double Gl = -0.9689*X +  1.8758*Y +  0.0415*Z;
    double Bl =  0.0557*X + (-0.2040)*Y +  1.0570*Z;
    Rl = qMax(0.0, Rl); Gl = qMax(0.0, Gl); Bl = qMax(0.0, Bl);
    const double desat=0.95, gray=(Rl+Gl+Bl)/3.0;
    Rl = desat*Rl + (1.0-desat)*gray;
    Gl = desat*Gl + (1.0-desat)*gray;
    Bl = desat*Bl + (1.0-desat)*gray;
    const int R = qBound(0, int(qRound(srgbCompand(Rl)*255.0)), 255);
    const int G = qBound(0, int(qRound(srgbCompand(Gl)*255.0)), 255);
    const int B = qBound(0, int(qRound(srgbCompand(Bl)*255.0)), 255);
    return QColor(R,G,B);
}
